home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / mail / editmbox < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  21.6 KB  |  748 lines

  1. #!/usr/local/bin/gawk -f
  2. #!/usr/bin/awk -f
  3. # @(#) editmbox.gawk 1.0 96/06/18
  4. # 92/08/31 john h. dubois iii (spcecdt@deeptht.armory.com)
  5. # 94/01/30 Fixed z command; was completely munged
  6. # 94/03/08 Use gawk so - options can be given
  7. # 94/07/22 Made "-" command work
  8.  
  9. BEGIN {
  10.     NumFiles = ProcArgs(ARGC,ARGV,"ho:dumns",Options) - 1
  11.     if ("h" in Options || NumFiles != 1) {
  12.     print \
  13. "editmbox: edit a mailbox, removing unwanted header lines and deleting\n" \
  14. "or saving messages.\n" \
  15. "Usage: editmbox [-hmudns] [-o<outfile>] <mailfile>\n" \
  16. "Output is put in <mailfile>.ed\n" \
  17. "Warning: this program is fragile.  If editing a large mailbox, save your\n"\
  18. "work occasionally using the Checkpoint function, and don't ever hit your\n"\
  19. "interrupt key!\n"\
  20. "Options:\n" \
  21. "-h: print this help\n" \
  22. "-o<outfile>: output is put in <outfile> instead of <mailfile>.ed\n" \
  23. "-m: don't complain about missing required fields in header\n" \
  24. "-u: don't complain about unrecognized fields in header\n" \
  25. "-d: don't discard any fields from header\n" \
  26. "-n: non-interactive: just discard unwanted fields from headers\n" \
  27. "-s: ignore EditStatus: field when reading in message file."
  28.  
  29.     exit(0)
  30.     }
  31.  
  32.     DFields = "Return-Path:|X-Mailer:|Received:|Status:|" \
  33. "Message-id:|Message-Id:|Message-ID:|References:|Reference:|"\
  34. "X-Newsgroups:|X-Organization:|X-Internet:|Resent-Message-Id:"\
  35. "X-Envelope-To:|X-Vms-To:|Distribution:|Errors-To:|Lines:"
  36.  
  37.     OFields = "Cc:|Bcc:|In-Reply-To:|Apparently-To:|To:|" \
  38. "Really-From:|Newsgroups:|Organization:|Sender:|Posted-Date:|Phone-Number:|" \
  39. "Address:|In-Real-Life:|Reply-To:|Source-Info:|Content-Type:|Content-Length:" \
  40.  
  41.     RFields = "From|From:|Subject:|Date:"
  42.  
  43.     if ("d" in Options)
  44.     OFields = OFields "|" DFields
  45.     else
  46.     MakeSet(DiscardFields,DFields,"|")
  47.     DiscardFields["EditStatus:"]
  48.  
  49.     if ("m" in Options)
  50.     OFields = OFields "|" RFields
  51.     else
  52.     MakeSet(ReqFields,RFields,"|")
  53.  
  54.     if ("u" in Options)
  55.     NoUnReq = 1
  56.     else
  57.     MakeSet(OptFields,OFields,"|")
  58.  
  59.     MakeSet(PrintFields,"From:|Subject:|To:|Cc:|Date:","|")
  60.  
  61.     if ((PAGER = ENVIRON["PAGER"]) == "")
  62.     PAGER = "more"
  63.     if ((EDITOR = ENVIRON["VISUAL"]) == "" && 
  64.     (EDITOR = ENVIRON["EDITOR"]) == "")
  65.     EDITOR = "vi"
  66.     MSep = "\1\1\1\1"
  67.     for (ind = 1; !(ind in ARGV); ind++)
  68.     ;
  69.     InputFile = ARGV[ind]
  70.     # Delete element so that we can read from tty as stdin (doesn't work!)
  71.     delete ARGV[ind]
  72.     ARGC = 0
  73.     if ("o" in Options)
  74.     OutFile = Options["o"]
  75.     else
  76.     OutFile = InputFile ".ed"
  77.     TmpName = InputFile ".tmp"
  78.     "tput lines" | getline DisplayLines
  79.     # Leave room for blank line & prompt line at end
  80.     DisplayLines -= 2
  81.  
  82.     for (NumMessages = 1; (Ret = \
  83.     GetMessage(InputFile,NumMessages,!("s" in Options))) == 1; NumMessages++)
  84.     printf "."
  85.     if (Ret == -1) {
  86.     printf "Could not open input file \"%s\".\n",InputFile
  87.     exit 1
  88.     }
  89.     close(InputFile)
  90.     NumMessages--
  91.     # Must read from /dev/tty to work around gawk bug
  92.     ChProc = "exec getch -ecp < /dev/tty"
  93.     ChProc | getline getchPID
  94.     print ""
  95.     for (i = 1; i <= NumMessages; i++)
  96.     if (!(i in Status))
  97.         Status[i] = " "
  98.     if ("n" in Options)
  99.     SaveMessages("S",OutFile,0)
  100.     else {
  101.     MakeUncontrolTable()
  102.     ProcMessages()
  103.     }
  104.     system("kill " getchPID)
  105. }
  106.  
  107. # Save all messages in Messages that have status Type to file File.
  108. # If WriteStatus is true, add an "EditStatus:" field to the header
  109. # of messages with status Saved and Deleted, with value "S" or "D".
  110. # The number of messages saved is printed and returned.
  111. function SaveMessages(Type,File,WriteStatus,  MessageNum,NumSaved) {
  112.     NumSaved = 0
  113.     if (Readable(File)) {
  114.     printf "File \"%s\" exists.  Overwrite? ",File
  115.     if (GetChar(1) !~ "[yY]") {
  116.         print "Aborting save."
  117.         return -1
  118.     }
  119.     }
  120.     for (MessageNum = 1; MessageNum <= NumMessages; MessageNum++)
  121.     if (Status[MessageNum] ~ "[" Type "]") {
  122.         print MSep > File
  123.         SaveMessage(MessageNum,File,WriteStatus)
  124.         print MSep > File
  125.         NumSaved++
  126.         printf "."
  127.     }
  128.     print ""
  129.     close(File)
  130.     printf "%s message(s) saved to %s.\n",NumSaved,File
  131.     return(NumSaved)
  132. }
  133.  
  134. # Read next message from file File.
  135. # The messages are stored in Messages[Num,1..numlines].
  136. # If LoadStatus is true and the message header has an EditStatus: field,
  137. # Status[Num] is set to its value.
  138. function GetMessage(File,Num,LoadStatus,  Line,Ret,LineNum,Mod) {
  139.     # A progress mark is printed every Mod lines.
  140.     Mod = 100
  141.     # Read & discard message separator
  142.     while ((Ret = (getline Line < File)) == 1 && Line == MSep)
  143.     ;
  144.     if (Ret != 1)
  145.     return Ret
  146.     
  147.     Messages[Num,LineNum = 1] = Line
  148.     while (getline Line < File == 1 && Line != MSep) {
  149.     Messages[Num,++LineNum] = Line
  150.     if (!(LineNum % Mod))
  151.         printf "%s\010",substr("|/-\\",LineNum / Mod % 4 + 1,1)
  152.     }
  153.     # Get rid of trailing blank lines
  154.     while (LineNum && Messages[Num,LineNum] ~ "^[ \t]*$")
  155.     delete Messages[Num,LineNum--]
  156.     LastLine[Num] = LineNum
  157.     Display[Num] = GetDisplay(Num,LoadStatus)
  158.     return 1
  159. }
  160.  
  161. # Build a display from message number Num & return it.
  162. # Uses ReqFields, OptFields, and PrintFields
  163. function GetDisplay(Num,LoadStatus,
  164. Fields,Field,LastField,HeadErrors,HeaderPrint,Line,LineNum,NumLines,
  165. BodyPrint,BodyLines,DisplaySize,RealLines) {
  166.  
  167.     RealLines = NumLines = LastLine[Num]
  168.     HeaderPrint = LastField = ""
  169.     # Include space between header & body, and Lines: line
  170.     DisplaySize = 2
  171.     for (LineNum = 1; LineNum <= NumLines &&
  172.     (Line = Messages[Num,LineNum]) != ""; LineNum++) {
  173.     split(Line,Fields)
  174.     if (Line ~ "^[ \t]")
  175.         Field = LastField
  176.     else
  177.         Field = Fields[1]
  178.     LastField = Field
  179.     if (LoadStatus == 1 && Field == "EditStatus:")
  180.         Status[Num] = Fields[2]
  181.     if (Field in DiscardFields) {
  182.         delete Messages[Num,LineNum]
  183.         RealLines--
  184.         continue
  185.     }
  186.     if (Field in PrintFields) {
  187.         HeaderPrint = HeaderPrint Line "\n"
  188.         DisplaySize++
  189.     }
  190.     if (Field in ReqFields)
  191.         ReqFields[Field] = 1
  192.     else if (!NoUnReq && !(Field in OptFields) && Field == Fields[1]) {
  193.         HeadErrors = HeadErrors "Unrecognized header field \"" Field "\".\n"
  194.         DisplaySize++
  195.     }
  196.     }
  197.     HeaderLines[Num] = LineNum - 1
  198.  
  199.     for (Field in ReqFields)
  200.     if (ReqFields[Field])
  201.         ReqFields[Field] = 0;
  202.     else {
  203.         HeadErrors = HeadErrors "No \"" Field "\" field in header.\n"
  204.         DisplaySize++
  205.     }
  206.     HeaderPrint = HeadErrors HeaderPrint
  207.  
  208.     BodyPrint = ""
  209.     if (NumLines == LineNum)
  210.     BodyLines = 0
  211.     else {
  212.     LineNum++
  213.     BodyLines = NumLines - LineNum
  214.  
  215.     for (; LineNum <= NumLines; LineNum++) {
  216.         Line = Messages[Num,LineNum]
  217.         gsub("\t","        ",Line)    # Worst-case tab expansion
  218.         DisplaySize += int(length(Line) / 80) + 1
  219.         if (DisplaySize <= DisplayLines)
  220.         BodyPrint = BodyPrint Messages[Num,LineNum] "\n"
  221.         else
  222.         break
  223.     }
  224.     }
  225.     Lines[Num] = RealLines
  226.     return HeaderPrint "Lines: " max(BodyLines,0) "\n\n" BodyPrint
  227. }
  228.  
  229. # Save message number MessageNum to file File.
  230. function SaveMessage(MessageNum,File,WriteStatus,
  231. LineNum,MLines,Line,MsgStatus,HLines) {
  232.     Mod = 100
  233.     MLines = LastLine[MessageNum]
  234.     HLines = HeaderLines[MessageNum]
  235.     for (LineNum = 1; LineNum <= HLines; LineNum++)
  236.     if (MessageNum SUBSEP LineNum in Messages)
  237.         print Messages[MessageNum,LineNum] > File
  238.     if (WriteStatus) {
  239.     MsgStatus = Status[MessageNum] 
  240.     if (MsgStatus !~ "[ U]")
  241.         printf "EditStatus: %s\n",MsgStatus > File
  242.     }
  243.     for (; LineNum <= MLines; LineNum++) {
  244.     print Messages[MessageNum,LineNum] > File
  245.     if (!(LineNum % Mod))
  246.         printf "%s\010",substr("|/-\\",LineNum / Mod % 4 + 1,1)
  247.     }
  248. }
  249.  
  250. # Return a string listing the options given in Words
  251. function OptionList(List,Words,  S,Len,i,Opt,OptLen,LineLen) {
  252.     Len = length(List)
  253.     S = Words[substr(List,1,1)]
  254.     LineLen = length(S)
  255.     for (i = 2; i <= Len; i++) {
  256.     Opt = substr(List,i,1)
  257.     OptLen = length(Words[Opt])
  258.     if (LineLen + OptLen > 78) {
  259.         S = S "\n" Words[Opt]
  260.         LineLen = OptLen
  261.     }
  262.     else {
  263.         S = S " " Words[Opt]
  264.         LineLen += OptLen + 1
  265.     }
  266.     }
  267.     return S
  268. }
  269.  
  270. # Return a string listing the options given in Words and Help
  271. function HelpList(List,Words,Help,  S,Len,i,c) {
  272.     Len = length(List)
  273.     c = substr(List,1,1)
  274.     S = sprintf("%-11s%s",Words[c],Help[c])
  275.     for (i = 2; i <= Len; i++) {
  276.     c = substr(List,i,1)
  277.     S = S sprintf("\n%-11s%s",Words[c],Help[c])
  278.     }
  279.     return S
  280. }
  281.  
  282. # Return the value of the Field field of message number Num,
  283. # or a null string if it does not exist
  284. function FindField(Num,Field,  Line) {
  285.     Line = FindHeaderLine(Num,Field "[ \t]")
  286.     return substr(Line,length(Field) + 1)
  287. }
  288.  
  289. # Return a line giving the number, status, and subject of message number Num
  290. function MessageLine(Num) {
  291.     return sprintf("%3d %s %s",Num,Status[Num],FindField(Num,"Subject:"))
  292. }
  293.  
  294. # Return a line giving statistics about the messages
  295. function MessageStats(  i,NumStat) {
  296.     for (i = 1; i <= NumMessages; i++)
  297.     NumStat[Status[i]]++
  298.     return sprintf("%d messages: %d saved, %d deleted, %d unmarked.", \
  299.     NumMessages,NumStat["S"],NumStat["D"], \
  300.     NumMessages - NumStat["S"] - NumStat["D"])
  301. }
  302.  
  303. # Return the first line in the header of message Num that matches Pattern
  304. function FindHeaderLine(Num,Pattern,  LineNum,Line,Lines) {
  305.     Lines = HeaderLines[Num]
  306.     for (LineNum = 1; LineNum <= Lines; LineNum++)
  307.     if ((Num SUBSEP LineNum in Messages) && \
  308.     ((Line = Messages[Num,LineNum]) ~ Pattern))
  309.         return Line
  310.     return ""
  311. }
  312.  
  313. # Global variables for static storage: OldFile
  314. function MessageCmd(Resp,MessageNum,Param,  i,LineNum,Sign) {
  315.  
  316.     CloseFile = ""
  317.  
  318.     if (Resp ~ "[sud]")
  319.     Status[MessageNum] = toupper(Resp)
  320.     else if (Resp == "l")
  321.     printf "%3d %s %s\n",MessageNum,Status[MessageNum], \
  322.     FindField(MessageNum,"Subject:")
  323.     else if (Resp == "f") {
  324.     if (Param == "")
  325.         Param = OldFile
  326.     if (Param == "")
  327.         print "Null filename."
  328.     else  {
  329.         print MSep >> Param
  330.         SaveMessage(MessageNum,Param,0)
  331.         print MSep > Param
  332.         CloseFile = Param
  333.     }
  334.     }
  335.     else if (Resp == "p") {
  336.     for (LineNum = 1; LineNum <= LastLine[MessageNum]; LineNum++)
  337.         if (MessageNum SUBSEP LineNum in Messages)
  338.         print Messages[MessageNum,LineNum] | PAGER
  339.     CloseFile = PAGER
  340.     }
  341.     else if (Resp ~ "[ve]") {
  342.     SaveMessage(MessageNum,TmpName,0)
  343.     close(TmpName)
  344.     system(EDITOR " " TmpName)
  345.     if (GetMessage(TmpName,MessageNum,0) == -1)
  346.         print ("Could not read temp file \"%s\".\n",TmpName)
  347.     close(TmpName)
  348.     system("rm " TmpName)
  349.     }
  350.     else if (Resp == "z") {
  351.     if (Param == "")
  352.         Param = "+0"
  353.     if (Param ~ "[-+]") {
  354.         Sign = (substr(Param,1,1) 1) + 0
  355.         Param = substr(Param,2)
  356.     }
  357.     else
  358.         Sign = 0
  359.     if (sign(Lines[MessageNum] - Param) == Sign)
  360.         printf "%4d %s\n",Lines[MessageNum],MessageLine(MessageNum)
  361.     }
  362.     return CloseFile
  363. }
  364.  
  365. function ProcMessages(  Resp,MessageNum,ValidOpt,NewNum,Param,
  366. OldPat,Words,Help,AllOpts,i,Error,PrevNum,OldNum,Direction,
  367. ListTo,ListFrom,Prompt,OldFile,LastMsg) {
  368.     AllOpts = "k,s,d,q,w,c,a,h,p,e,r,b,u,l,f,-,/,?,#,z"
  369.     InitArr(Words,AllOpts, \
  370. "sKip,Save,Delete,Quit,Write,Checkpoint,Abort,Help,Page,Edit,Redraw," \
  371. "Back,Unmark,List,File,-previous,/search,?search,#info,siZe",",")
  372.     gsub(",","\n",AllOpts)
  373.     InitArr(Help,AllOpts, \
  374. "Skip over message without marking it.\n" \
  375. "Mark message to be saved.\n" \
  376. "Mark message to be deleted.\n" \
  377. "Write messages marked to be saved to the output mailbox and quit.\n" \
  378. "Save all messages and markings to a new mailbox.\n" \
  379. "Write unmarked & save-marked messages and markings to a new mailbox.\n" \
  380. "Exit from this program without saving anything.\n" \
  381. "Print this help.\n" \
  382. "Pipe entire message into pager ($PAGER or \"more\")\n" \
  383. "Invoke editor $VISUAL, $EDITOR, or \"vi\" on message (also: v).\n" \
  384. "Print the display excerpt of the message again (also: ^L, ^R).\n" \
  385. "Go back one message.\n" \
  386. "Remove any marking of the message.\n" \
  387. "List status & subjects of messages (also: =).\n" \
  388. "Save message to a file (appending if it exists) in mailbox format.\n" \
  389. "Go to the previous message displayed.\n" \
  390. "Search forward for messages with a pattern in the header.\n" \
  391. "Search backward for messages with a pattern in the header.\n" \
  392. "Print statistics about messages.\n" \
  393. "Find messages by number of lines (enter lines, +lines, or -lines)." \
  394. ,"\n")
  395.     gsub("\n","",AllOpts)
  396.     AllOpts = AllOpts
  397.  
  398.     "clear" | getline ClearSeq
  399.     MessageNum = 1
  400.     PrevNum = 0
  401.     InitArr(OptTrans,"v,=,\014,\022","e,l,r,r",",")
  402.     InitArr(Prompts,"c,w,f","Mailbox name: ,Mailbox name: ,File name: ",",")
  403.     ParamOpts = "123456789/?cwfz"
  404.     while (1) {
  405.     LastMsg = max(PrevNum,1)
  406.     if (PrevNum != MessageNum) {
  407.         if (MessageNum > NumMessages)
  408.         ValidOpt = "hqwcabl-?#"
  409.         else {
  410.         ValidOpt = AllOpts
  411.         print ClearSeq Display[MessageNum]
  412.         }
  413.         PrevNum = MessageNum
  414.     }
  415.     if (MessageNum > NumMessages)
  416.         Prompt = sprintf("No more messages: [%s] %s",ValidOpt,Error)
  417.     else
  418.         Prompt = sprintf("%s %3d [%s] %s",Status[MessageNum], \
  419.         MessageNum,ValidOpt,Error)
  420.     Error = ""
  421.     Resp = GetCommand(ValidOpt "123456789",Prompt,OptTrans, \
  422.     ParamOpts,Prompts,OptionList(ValidOpt,Words))
  423.     Param = substr(Resp,2)
  424.     Resp = substr(Resp,1,1)
  425.     if (Resp ~ "[1-9]") {
  426.         NewNum = Resp Param
  427.         if (NewNum !~ "[0-9]+") {
  428.         print "Bad number."
  429.         continue
  430.         }
  431.         NewNum += 0
  432.         if (NewNum > NumMessages)
  433.         print "Not that many messages."
  434.         else
  435.         MessageNum = NewNum
  436.     }
  437.     else if (Resp ~ "[k ]")
  438.         MessageNum++
  439.     else if (Resp == "a") {
  440.         printf "Abort without saving... are you sure? [yn] "
  441.         Resp = tolower(GetChar(1))
  442.         if (Resp == "y")
  443.         return
  444.     }
  445.     else if (Resp == "q") {
  446.         for (i = 1; i <= NumMessages && Status[i] ~ "[SD]"; i++)
  447.         ;
  448.         if (i > NumMessages) {
  449.         if (SaveMessages("S",OutFile,0) != -1)
  450.             return
  451.         }
  452.         else {
  453.         Error = "(Unmarked Message) "
  454.         MessageNum = i
  455.         }
  456.     }
  457.     else if (Resp ~ "[cw]") {
  458.         if (Param == "")
  459.         Param = OldFile
  460.         if (Param == "")
  461.         print "Null filename."
  462.         else  {
  463.         if (Resp == "c")
  464.             Stat = " SU"
  465.         else
  466.             Stat = " SUD"
  467.         SaveMessages(Stat,Param,1)
  468.         }
  469.     }
  470.     else if (Resp == "h")
  471.         print OptionList(ValidOpt,Words) "\n" HelpList(ValidOpt,Words,Help)
  472.     else if (Resp == "r")
  473.         print ClearSeq Display[MessageNum]
  474.     else if (Resp == "b")
  475.         MessageNum = max(1,MessageNum - 1)
  476.     else if (Resp == "-")
  477.         MessageNum = LastMsg
  478.     else if (Resp == "#")
  479.         print MessageStats()
  480.     else if (Resp == "?" || Resp == "/") {
  481.         if (Param == "")
  482.         Param = OldPat
  483.         if (Param == "") {
  484.         print "Null pattern."
  485.         continue
  486.         }
  487.         OldPat = Param
  488.         Direction = (Resp == "/") * 2 - 1
  489.         Count = 0
  490.         for (i = MessageNum; 1 <= i && i <= NumMessages && \
  491.         Count < DisplayLines; i += Direction)
  492.         if (FindHeaderLine(i,Param) != "") {
  493.             print MessageLine(i)
  494.             Count++
  495.         }
  496.     }
  497.     else if (Resp ~ "[sud]")
  498.         MessageCmd(Resp,MessageNum++,Param)
  499.     else if (Resp == "l") {
  500.         ListTo = min(NumMessages,MessageNum + DisplayLines - 1)
  501.         ListFrom = max(1,ListTo - DisplayLines + 1)
  502.         for (i = ListFrom; i <= ListTo; i++)
  503.         MessageCmd(Resp,i,"")
  504.     }
  505.     else if (Resp ~ "[vefp]") {
  506.         if ((CloseFile = MessageCmd(Resp,MessageNum,Param)) != "")
  507.         close(CloseFile)
  508.     }
  509.     else if (Resp == "z") {
  510.         if (Param !~ "[-+1-9][0-9]+") {
  511.         print "Bad range."
  512.         continue
  513.         }
  514.         for (i = 1; i <= NumMessages; i++)
  515.         MessageCmd(Resp,i,Param)
  516.     }
  517.     else 
  518.         print "uh oh..."
  519.     }
  520. }
  521.  
  522. # Library Routines
  523.  
  524. # MakeSet: make a set from a list.
  525. # An index with the name of each element of the list
  526. # is created in the given array.
  527. # Input variables: 
  528. # Elements is a string containing the list of elements.
  529. # Sep is the character that separates the elements of the list.
  530. # Output variables:
  531. # Set is the array.
  532. function MakeSet(Set,Elements,Sep,  Num,Names) {
  533.     Num = split(Elements,Names,Sep)
  534.     for (; Num; Num--)
  535.     Set[Names[Num]];
  536. }
  537.  
  538. function min(a,b) {
  539.     if (a < b)
  540.     return a
  541.     else
  542.     return b
  543. }
  544.  
  545. function max(a,b) {
  546.     if (a > b)
  547.     return a
  548.     else
  549.     return b
  550. }
  551.  
  552. # InitArr: Initialize an array with values.
  553. # Ind and Vals are separated into lists on Sep.
  554. # For each item in Ind, an index with that name is created in Arr[],
  555. # and the value with the same position in Vals is stored in it.
  556. # Global variables: none.
  557. function InitArr(Arr,Ind,Vals,sep,  numind,indnames,values) {
  558.     split(Ind,indnames,sep)
  559.     split(Vals,values,sep)
  560.     for (numind in indnames)
  561.     Arr[indnames[numind]] = values[numind]
  562. }
  563.  
  564. # ProcArgs.awk   john h. dubois iii   92/02/29
  565. # optlist is a string which contains all of the possible command line options.
  566. # If a character is followed by a colon, 
  567. # it indicates that that option takes an argument.
  568. # Strings in argv[] which begin with "-" or "+" are taken to be
  569. # strings of options, except that a string which consists solely of "-" or
  570. # "+" is not taken to be an option string (it is not acted on).
  571. # If an option takes an argument, the argument may either immedately
  572. # follow it or be given separately.
  573.  
  574. # If an option that does not take an argument is given,
  575. # an index with its name is created in options and its value is set to "1".
  576. # If an option that does take an argument is given,
  577. # an index with its name is created in options and its value
  578. # is set to the value of the argument given for it.
  579. # Options and their arguments are deleted from argv.
  580. # Note that this means that there may be gaps 
  581. # left in the indices of argv[].
  582. # argv[0] is not examined.
  583. # An argument of "--" or "++" stops the scanning of argv.
  584. # The number of arguments left in argc is returned.
  585. # If an error occurs,
  586. # the string OptErr is set to an error message and -1 is returned.
  587. function ProcArgs(argc,argv,optlist,options,  
  588. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos) {
  589. # ArgNum is the index of the argument being processed.
  590. # ArgsLeft is the number of arguments left in argv.
  591. # Arg is the argument being processed.
  592. # ArgLen is the length of the argument being processed.
  593. # ArgInd is the position of the character in Arg being processed.
  594. # Option is the character in Arg being processed.
  595. # Pos is the position in optlist of the option being processed.
  596.     ArgsLeft = argc
  597.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  598.     Arg = argv[ArgNum]
  599.     if (Arg ~ "^[-+]") {
  600.         if ((Arg == "-") || (Arg == "+"))
  601.         continue
  602.         delete argv[ArgNum]
  603.         ArgsLeft--
  604.         if ((Arg == "--") || (Arg == "++"))
  605.         break
  606.         ArgLen = length(Arg)
  607.         for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  608.         Option = substr(Arg,ArgInd,1)
  609.         Pos = index(optlist,Option)
  610.         if (!Pos) {
  611.             OptErr = "Invalid option: -" Option
  612.             return -1
  613.         }
  614.         if (substr(optlist,Pos + 1,1) == ":") {
  615.             if (ArgInd < ArgLen) {
  616.             options[Option] = substr(Arg,ArgInd + 1)
  617.             ArgInd = ArgLen
  618.             }
  619.             else {
  620.             if (ArgNum < (argc - 1)) {
  621.                 options[Option] = argv[++ArgNum]
  622.                 delete argv[ArgNum]
  623.                 ArgsLeft--
  624.             }
  625.             else {
  626.                 OptErr = "Option -" Option " requires an argument."
  627.                 return -1
  628.             }
  629.             }
  630.         }
  631.         else
  632.             options[Option] = 1
  633.         }
  634.     }
  635.     }
  636.     return ArgsLeft
  637. }
  638.  
  639. # Uncontrol(S): Convert control characters in S to symbolic form.
  640. # Characters in S with values < 32 are converted to the form ^X.
  641. # The resulting string is returned.
  642. # Global variables: the array UncTable must be initialized to a
  643. # lookup table translating characters 0..31 to symbolic values
  644. # before using this function.
  645. function Uncontrol(S,  c,i,len,Output) {
  646.     len = length(S)
  647.     Output = ""
  648.     for (i = 1; i <= len; i++) {
  649.     c = substr(S,i,1)
  650.     if (c in UncTable)
  651.         Output = Output UncTable[c]
  652.     else
  653.         Output = Output c
  654.     }
  655.     return Output
  656. }
  657.  
  658. # MakeUncontrolTable: Make a table for use by Uncontrol().
  659. # Global variables: 
  660. # UncTable[] is made into a character -> symbolic character lookup table.
  661. function MakeUncontrolTable(  i) {
  662.     for (i = 0; i < 32; i++)
  663.     UncTable[sprintf("%c",i)] = "^" sprintf("%c",i + 64)
  664. }
  665.  
  666. function sign(value) {
  667.     if (value > 0)
  668.     return 1
  669.     else if (value < 0)
  670.     return -1
  671.     else
  672.     return 0
  673. }
  674.  
  675. # Read & return a line.
  676. # Needed because awk won't read stdin if any args were given.
  677. # If the read fails, an error message is printed and Err is returned.
  678. function TTYLine(Err,  Ret,ReadLine,Line) {
  679.     # Must read from /dev/tty due to gawk bug
  680.     ReadLine = "read line < /dev/tty && echo \"$line\""
  681.     Ret = (ReadLine | getline Line)
  682.     close(ReadLine)
  683.     if (Ret == 1) 
  684.     return Line
  685.     else {
  686.     print "Couldn't read from tty!"
  687.     return Err
  688.     }
  689. }
  690.  
  691. # Get a character from stdin in non-canonical mode & return it.
  692. # The character is echoed.
  693. # If PrintNewline is 1, a newline is printed after the character is read.
  694. function GetChar(PrintNewline,  Resp) {
  695.     ChProc | getline Resp
  696.     if (PrintNewline)
  697.     print ""
  698.     return Resp
  699. }
  700.  
  701. # Returns 0 if FileName can't be read, else nonzero.
  702. function Readable(FileName,  foo,Ret) {
  703.     Ret = getline foo < FileName
  704.     close(FileName)
  705.     return (Ret != -1)
  706. }
  707.  
  708. # Wait for a key to be pressed & return it.
  709. # Prompt is printed to prompt for a key.
  710. # Options is a string containing all of the valid commands.
  711. # KeyMap is an array mapping command synonyms to characters in Options.
  712. # Help is a string to print if an invalid key is pressed.
  713. # If space is pressed, it is mapped to the first character in Options.
  714. # If a character in the string ParamOpts is pressed,
  715. # a string is read and appended to the key in the return value.
  716. # If the character is an index of the array Prompts,
  717. # the value in Prompts for the character is printed before reading the string.
  718. # If the key pressed is not in ParamOpts,
  719. # a newline is printed before returning.
  720.  
  721. function GetCommand(Options,Prompt,KeyMap,ParamOpts,Prompts,Help,
  722. Done,Key,Cmd,Param) {
  723.     Done = 0
  724.     while (!Done) {
  725.     printf "%s",Prompt
  726.     Key = GetChar()
  727.     if (Key == " ")
  728.         Cmd = Key = substr(Options,1,1)
  729.     else
  730.         Cmd = tolower(Key)
  731.     Key = Uncontrol(Key)
  732.     if (Cmd in KeyMap)
  733.         Cmd = KeyMap[Cmd]
  734.     if (index(Options,Cmd)) {
  735.         if (index(ParamOpts,Cmd)) {
  736.         if (Cmd in Prompts)
  737.             printf "\n%s",Prompts[Cmd]
  738.         if ((Param = TTYLine("\200")) != "\200")
  739.             return Cmd Param
  740.         }
  741.         print ""
  742.         return Cmd
  743.     }
  744.     else
  745.         printf "\nInvalid command '%s'\n%s\n",Key,Help
  746.     }
  747. }
  748.